-module(npc_client).
-export([start/0]).

start() ->
	ErrLog = error_logs:start_error_log("npc.log"),
	error_logs:error_log(ErrLog, "Start NP service...~n"),
	{_, _, LoopInterval} = npc_config:npc_config(),
	error_logs:error_log(ErrLog, "Load Configure...~n"),
	case timer:send_after(LoopInterval, timeout) of
		{ok, _} ->
			server_loop(ErrLog);
		{error, Reason} ->
			error_logs:error_log(ErrLog, "Server Loop Fail(~p).~n", Reason)
	end.

server_loop(ErrLog) ->
	receive
		timeout ->
			error_logs:error_log(ErrLog, "Server Loop Timeout, Start Receive Email...~n"),
			{{Mail, Pwd}, {Server, Port}, LoopInterval} = npc_config:npc_config(),
			try mails:mail_client(Server, Port, Mail, Pwd, true) of
				{mail_ok, Mails} ->
					case get_server_info(Mails) of
						{npc_server_match, ServerInfo} ->
							TPid = spawn(fun() -> login_server(ErrLog, ServerInfo) end),
							link(TPid),
							server_loop(ErrLog);
						npc_server_nomatch ->
							error_logs:error_log(ErrLog, "No Command Email.~n"),
							case timer:send_after(LoopInterval, timeout) of
								{ok, _} ->
									server_loop(ErrLog);
								{error, Reason} ->
									error_logs:error_log(ErrLog, "Server Loop Fail(~p).~n", Reason)
							end
					end
			catch
				throw:{mail_error, Reason} ->
					error_logs:error_log(ErrLog, "Get Mail Error(~p).~n", [Reason]),
					case timer:send_after(LoopInterval, timeout) of
						{ok, _} ->
							server_loop(ErrLog);
						{error, Reason} ->
							error_logs:error_log(ErrLog, "Server Loop Fail(~p).~n", Reason)
					end;
				_:_ ->
					case timer:send_after(LoopInterval, timeout) of
						{ok, _} ->
							server_loop(ErrLog);
						{error, Reason} ->
							error_logs:error_log(ErrLog, "Server Loop Fail(~p).~n", Reason)
					end
			end;
		{'EXIT', _, _} ->
			error_logs:error_log(ErrLog, "Login Server Exit.~n"),
			{{_, _}, {_, _}, LoopInterval} = npc_config:npc_config(),
			case timer:send_after(LoopInterval, timeout) of
				{ok, _} ->
					server_loop(ErrLog);
				{error, Reason} ->
					error_logs:error_log(ErrLog, "Server Loop Fail(~p).~n", Reason)
			end;	
		Any ->
			error_logs:error_log(ErrLog, "Server Loop Unknown Message(~p).~n", Any),
			{{_, _}, {_, _}, LoopInterval} = npc_config:npc_config(),
			case timer:send_after(LoopInterval, timeout) of
				{ok, _} ->
					server_loop(ErrLog);
				{error, Reason} ->
					error_logs:error_log(ErrLog, "Server Loop Fail(~p).~n", Reason)
			end
	end.

get_server_info([H|T]) ->
	case re:run(H, "^([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3}):([0-9]*)$") of
		{match, [_, {AP, AL}, {BP, BL}, {CP, CL}, {DP, DL}, {PP, PL}]} ->
			{A, _} = string:to_integer(string:substr(H, AP + 1, AL)),
			{B, _} = string:to_integer(string:substr(H, BP + 1, BL)),
			{C, _} = string:to_integer(string:substr(H, CP + 1, CL)),
			{D, _} = string:to_integer(string:substr(H, DP + 1, DL)),
			{P, _} = string:to_integer(string:substr(H, PP + 1, PL)),
			{npc_server_match, {{A, B, C, D}, P}};
		nomatch ->
			get_server_info(T)
	end;
get_server_info([]) ->
	npc_server_nomatch.

login_server(ErrLog, {Address, Port}) ->
	error_logs:error_log(ErrLog, "Connect NP server(~p ~p).~n", [Address, Port]),
	case gen_tcp:connect(Address, Port, [binary, inet, {active, false}, {keepalive, true}]) of
		{ok, Socket} ->
			NodeName = net_adm:localhost(),
			DataLen = string:len(NodeName),
			LoginPackage = list_to_binary([<<1:8/big, DataLen:16/big>>, NodeName]),
			case gen_tcp:send(Socket, LoginPackage) of
				ok ->
					case gen_tcp:recv(Socket, 1) of
						{ok, LoginResponsePackage} ->
							<<State:1/big, Mode:7/big>> = LoginResponsePackage,
							case State of
								1 ->
									network_proxy(ErrLog, Socket, Mode);
								0 ->
									gen_tcp:shutdown(Socket, read_write),
									gen_tcp:close(Socket),
									error_logs:error_log(ErrLog, "Server Return Fail.~n")
							end;
						{error, Reason} ->
							gen_tcp:close(Socket),
							error_logs:error_log(ErrLog, "Receive Server ACK Fail(~p).~n", [Reason])
					end;
				{error, Reason} ->
					gen_tcp:close(Socket),
					error_logs:error_log(ErrLog, "Login Server Fail(~p).~n", [Reason])
			end;
		{error, Reason} ->
			error_logs:error_log(ErrLog, "Connect Fail(~p).~n", [Reason])
	end.

network_proxy(ErrLog, Socket, Mode) ->
	case Mode of
		0 ->
			error_logs:error_log(ErrLog, "Start stream Mode.~n"),
			case gen_tcp:recv(Socket, 6) of
				{ok, StreamPackage} ->
					<<A:8/big, B:8/big, C:8/big, D:8/big, P:16/big>> = StreamPackage,
					FPid = self(),
					PPid = spawn(fun() -> local_proxy_connect(ErrLog, {{A, B, C, D}, P}, FPid, Socket) end),
					receive
						{local_proxy_success, LPSocket} ->
							gen_tcp:send(Socket, <<2:8/big, 1:8/big, 0:16/big>>),
							case inet:setopts(Socket, [{active, true}]) of
								ok ->
									network_proxy_loop(ErrLog, Socket, PPid, LPSocket);
								{error, _} ->
									gen_tcp:shutdown(Socket, read_write),
									gen_tcp:close(Socket)
							end;
						{local_proxy_fail, Reason} ->
							DataLen = string:len(Reason),
							gen_tcp:send(Socket, list_to_binary([<<2:8/big, 0:8/big, DataLen:16/big>>, Reason])),
							gen_tcp:shutdown(Socket, read_write),
							gen_tcp:close(Socket)
					end;
				{error, Reason} ->
					gen_tcp:close(Socket),
					error_logs:error_log(ErrLog, "Receive Server Stream Info Fail(~p).~n", [Reason])					
			end;
		1 ->
			error_logs:error_log(ErrLog, "Start http Mode.~n"),
			gen_tcp:shutdown(Socket, read_write),
			gen_tcp:close(Socket);
		_ ->
			error_logs:error_log(ErrLog, "Unknown Mode(~p).~n", [Mode]),
			gen_tcp:shutdown(Socket, read_write),
			gen_tcp:close(Socket)
	end.

network_proxy_loop(ErrLog, Socket, PPid, LPSocket) ->
	receive
		{tcp, Socket, Binary} ->
			gen_tcp:send(LPSocket, Binary),
			network_proxy_loop(ErrLog, Socket, PPid, LPSocket);
		{tcp_closed,Socket} ->
			error_logs:error_log(ErrLog, "Network Socket Closed.~n"),
			gen_tcp:close(Socket),
			PPid ! {network_proxy, self(), shutdown};
		{local_proxy, PPid, shutdown} ->
			error_logs:error_log(ErrLog, "Local Socket Closed.~n"),
			gen_tcp:shutdown(Socket, read_write),
			gen_tcp:close(Socket)
	end.

local_proxy_connect(ErrLog, {Address, Port}, FPid, NPSocket) ->
	case gen_tcp:connect(Address, Port, [binary, inet]) of
		{ok, Socket} ->
			FPid ! {local_proxy_success, Socket},
			local_proxy_loop(ErrLog, Socket, FPid, NPSocket);
		{error, Reason} ->
			error_logs:error_log(ErrLog, "Proxy Connect Fail(~p).~n", [Reason]),
			FPid ! {local_proxy_fail, atom_to_list(Reason)}
	end.

local_proxy_loop(ErrLog, Socket, FPid, NPSocket) ->
	receive
		{tcp, Socket, Binary} ->
			gen_tcp:send(NPSocket, Binary),
			local_proxy_loop(ErrLog, Socket, FPid, NPSocket);
		{tcp_closed,Socket} ->
			error_logs:error_log(ErrLog, "Local Socket Closed.~n"),
			gen_tcp:close(Socket),
			FPid ! {local_proxy, self(), shutdown};
		{network_proxy, FPid, shutdown} ->
			error_logs:error_log(ErrLog, "Network Socket Closed.~n"),
			gen_tcp:shutdown(Socket, read_write),
			gen_tcp:close(Socket)
	end.
